/**
 * Aibton.com Inc.
 * Copyright (c) 2004-2017 All Rights Reserved.
 */
package com.aibton.mybatis.share.interceptor;

import java.sql.Connection;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;

import com.aibton.mybatis.share.config.ShareConstantKey;
import com.aibton.mybatis.share.entity.ShareData;
import com.aibton.mybatis.share.exception.MybatisShareException;
import com.aibton.mybatis.share.table.IShareTableRule;
import com.aibton.mybatis.share.utils.MybatisShareThreadLocalHolder;
import com.aibton.mybatis.share.utils.ReflectUtil;

/**
 * mybatis分库分表插件
 * @author huzhihui
 * @version v 0.1 2017/6/2 21:53 huzhihui Exp $$
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class,
                                                                                     Integer.class }) })
public class ShareInterceptor implements Interceptor {

    /**
     * 需要分库分表的表集合
     */
    private List<String>    tables;

    /**
     * 是否分库：默认false
     * true：是
     * false：否
     */
    private boolean         shareDatabase = false;

    /**
     * 分表规则对象
     */
    private IShareTableRule iShareTableRule;

    /**
     * 拦截后要执行的方法
     */
    public Object intercept(Invocation invocation) throws Throwable {
        RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
        //通过反射获取到当前RoutingStatementHandler对象的delegate属性
        StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler,
            "delegate");
        //获取到当前StatementHandler的 boundSql，这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的，因为之前已经说过了
        //RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
        BoundSql boundSql = delegate.getBoundSql();
        String sql = boundSql.getSql();
        String shareSql = bulidShareSql(sql, MybatisShareThreadLocalHolder.getShareDate());
        ReflectUtil.setFieldValue(boundSql, "sql", shareSql);
        return invocation.proceed();
    }

    /**
     * 拦截器对应的封装原始对象的方法
     */
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    /**
     * 设置注册拦截器时设定的属性
     */
    public void setProperties(Properties properties) {

        try {
            //
            String shareDatabaseStr = properties.getProperty(ShareConstantKey.SHARE_DATABASE,
                "false");
            if (Boolean.valueOf(shareDatabaseStr)) {
                this.shareDatabase = Boolean.parseBoolean(shareDatabaseStr);
            }
            //
            String tablesStr = properties.getProperty(ShareConstantKey.TABLES, null);
            if (null != tablesStr && !tablesStr.trim().equals("")) {
                String tables[] = tablesStr.split(",");
                this.tables = Arrays.asList(tables);
            }
            //分表规则对象构件
            String shareTableRuleStr = properties.getProperty(ShareConstantKey.SHARE_TABLE_RULE,
                null);
            if (null != shareTableRuleStr && !shareTableRuleStr.trim().equals("")) {
                Class<?> clazz = Class.forName(shareTableRuleStr);
                this.iShareTableRule = (IShareTableRule) clazz.newInstance();
                if (null == iShareTableRule) {
                    throw new MybatisShareException("分表规则初始化失败，请检查传入数据");
                }
            }
        } catch (Exception ex) {

        }

    }

    /**
     * 修正sql，返回分表成功的sql
     * @param sql
     * @param shareData
     * @return
     */
    public String bulidShareSql(String sql, ShareData shareData) {
        //格式化sql
        sql = sql.replace("\n", " ");
        //没有设置分表信息
        if (null == shareData || shareData.getAllowShare().equals(false)
            || null == shareData.getShareTableSuffix()) {
            return sql;
        }
        String shareSql = "";
        String tmpSql = sql.toLowerCase();
        String tableSuffix = shareData.getShareTableSuffix();
        //获取优先级最高的分表后缀
        if (shareData.getSupperShareTableSuffix() != null) {
            tableSuffix = shareData.getSupperShareTableSuffix();
        }
        //如果是插入数据
        if (tmpSql.indexOf("insert") == 0) {
            int tableNameStartIndex = tmpSql.indexOf(" into ") + 6;
            int tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            while (tableNameEndIndex == tableNameStartIndex) {
                tableNameStartIndex++;
                tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            }
            String preSql = tmpSql.substring(0, tableNameStartIndex);
            String tableName = tmpSql.substring(tableNameStartIndex, tableNameEndIndex);
            tableName = tableName + tableSuffix;
            String sufSql = tmpSql.substring(tableNameEndIndex);
            shareSql = preSql + tableName + sufSql;
        } else if (tmpSql.indexOf("select") == 0) {
            int tableNameStartIndex = tmpSql.indexOf(" from ") + 6;
            int tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            while (tableNameEndIndex == tableNameStartIndex) {
                tableNameStartIndex++;
                tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            }
            String preSql = tmpSql.substring(0, tableNameStartIndex);
            String tableName = tmpSql.substring(tableNameStartIndex, tableNameEndIndex);
            tableName = tableName + tableSuffix;
            String sufSql = tmpSql.substring(tableNameEndIndex);
            shareSql = preSql + tableName + sufSql;
        } else if (tmpSql.indexOf("update") == 0) {
            int tableNameStartIndex = tmpSql.indexOf(" update ") + 8;
            int tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            while (tableNameEndIndex == tableNameStartIndex) {
                tableNameStartIndex++;
                tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            }
            String preSql = tmpSql.substring(0, tableNameStartIndex);
            String tableName = tmpSql.substring(tableNameStartIndex, tableNameEndIndex);
            tableName = tableName + tableSuffix;
            String sufSql = tmpSql.substring(tableNameEndIndex);
            shareSql = preSql + tableName + sufSql;
        } else if (tmpSql.indexOf("delete") == 0) {
            int tableNameStartIndex = tmpSql.indexOf(" from ") + 6;
            int tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            while (tableNameEndIndex == tableNameStartIndex) {
                tableNameStartIndex++;
                tableNameEndIndex = tmpSql.indexOf(" ", tableNameStartIndex);
            }
            String preSql = tmpSql.substring(0, tableNameStartIndex);
            String tableName = tmpSql.substring(tableNameStartIndex, tableNameEndIndex);
            tableName = tableName + tableSuffix;
            String sufSql = tmpSql.substring(tableNameEndIndex);
            shareSql = preSql + tableName + sufSql;
        } else {
            throw new MybatisShareException("该sql语句不是标准语句，分表拦截失败");
        }
        MybatisShareThreadLocalHolder.clear();
        return shareSql;
    }

}
